/*	Copyright 2007 - Xavier Baro (xbaro@cvc.uab.cat)

	This file is part of eapmlib.

    Eapmlib is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or any 
	later version.

    Eapmlib is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "EvolLearner.h"
#include "math.h"

Evolutive::CEvolLearner::CEvolLearner(void) : m_MaxNumIterations(-1),m_OptCriteria(OPT_MINIMIZE),m_PopulationSize(0),m_CodingMethod(CODE_GRAY),m_IterNum(0),m_NumElitist(0),m_StopValueEnabled(false),m_StopValue(0),m_SelMethod(SELMETHOD_SCORE),m_InitialPopulation(NULL),m_StableIters(0)
{
	m_StableIters=50;
}

Evolutive::CEvolLearner::~CEvolLearner(void)
{
}

void Evolutive::CEvolLearner::InitLearner(void)
{
}

void Evolutive::CEvolLearner::SetStopValue(double Value)
{
	m_StopValueEnabled=true;
	m_StopValue=Value;
}
void Evolutive::CEvolLearner::SetMaxIterations(int NumIterations)
{
	m_MaxNumIterations=NumIterations;
}

void Evolutive::CEvolLearner::SetOptCriteria(OPT_CRITERIA Criteria)
{
	m_OptCriteria=Criteria;
}

void Evolutive::CEvolLearner::SetNumElitist(int NumIndividuals)
{
	//! Sets the new number
	m_NumElitist=NumIndividuals;
}

double Evolutive::CEvolLearner::Solve(void)
{
	CChromosome C;
	int Iteration;

	return Solve(C,Iteration);
}

double Evolutive::CEvolLearner::Solve(Evolutive::CChromosome &Solution)
{
	int Iteration;

	return Solve(Solution,Iteration);
}

double Evolutive::CEvolLearner::Solve(int &Iteration)
{
	CChromosome C;

	return Solve(C,Iteration);
}


double Evolutive::CEvolLearner::Solve(Evolutive::CChromosome &Solution,int &Iteration)
{
	bool Stop;
	double IterScore;	
	
	//! Initialize the learner
	InitLearner();	

	//! Set the number of iterations to 0
	m_IterNum=0;

	//! Initialize the parent population
	InitPopulation();

	//! Begins the evolution process
	Stop=false;
	m_StableCount=0;
	while(!Stop)
	{
		//! Update the iteration number
		m_IterNum++;

		/*! Go to the next generation. This method is a pure virtual method, which is 
		implemented by the derived classes in function of their own criterias. The result 
		is a new population.
		*/
		NextGeneration();
		
		//! Add the elitist individuals
		m_Population.AddElitistMembers();
		
		//! Evalue the new generation
		EvalPopulation();
		
		//! Store the elitist individuals
		m_Population.SelectElitistMembers();
		
		//! Store the best solution and best score	
		if(m_IterNum==1)
		{	
			//! Store the best value
			m_BestScore=m_Population.GetSortedScoreVal(0);
			m_BestIndividual=*m_Population.GetSortedChromosomePtr(0);
			m_StableCount=0;
		}
		else
		{
			//! If the best value is better than the stored one, store it.
			IterScore=m_Population.GetSortedScoreVal(0);

			switch(m_OptCriteria)
			{
			case Evolutive::OPT_MINIMIZE: 
				if(IterScore<m_BestScore)
				{
					m_BestScore=m_Population.GetSortedScoreVal(0);
					m_BestIndividual=*m_Population.GetSortedChromosomePtr(0);
					m_StableCount=0;
				}		
				else
				{
					m_StableCount++;
				}
				break;
			case Evolutive::OPT_MAXIMIZE:
				if(IterScore>m_BestScore)
				{
					m_BestScore=m_Population.GetSortedScoreVal(0);
					m_BestIndividual=*m_Population.GetSortedChromosomePtr(0);
					m_StableCount=0;
				}
				else
				{
					m_StableCount++;
				}
				break;
			}
		}

		cout << "[" << m_IterNum << "] " << m_BestScore << "\r";
		cout.flush();

		//! Check the stop criterias.
		Stop=ApplyStopCriteria();
	}

	//! Assign the best solution
	Solution=m_BestIndividual;

	//! Store the number of iterations
	Iteration=m_IterNum;

	//! Return de best value
	return m_BestScore;
}

void Evolutive::CEvolLearner::InitPopulation(void)
{
	int *FieldsDesc=NULL;
	int NumFields;

	//! Obtain the problem description from the Evaluator
	FieldsDesc=m_Evaluator->GetProblemDesc(NumFields);

	//! Initialize the population
	m_Population.Initialize(m_PopulationSize,NumFields,FieldsDesc,m_CodingMethod);

	//! If a priori knowledge is available use it
	UseAprioriPopulation();
	
	//! Use the evaluator to change the invalid individuals
	ValidatePopulation();

	//! Sets the elitism parameters
	m_Population.SetNumElitist(m_NumElitist);
	
	//! Evalue the population
	EvalPopulation();

	//! Store the elitist individuals
	m_Population.SelectElitistMembers();
	
	//! Initialize the best individual
	m_BestIndividual.SetFields(NumFields,FieldsDesc);
	m_BestIndividual.SetCode(m_CodingMethod);
}

void Evolutive::CEvolLearner::ValidatePopulation(void)
{
	register int i;
	CChromosome *pC=NULL;
	int MaxTry=10000,Count;

	for(i=0;i<m_PopulationSize;i++)
	{
		//! Point to the chromosome
		pC=m_Population.GetChromosomePtr(i);

		//! Generate random chromosomes to obtain a valid chromosome
		Count=0;
		while(!m_Evaluator->IsValid(pC) && Count<MaxTry)
		{
			pC->Generate();
			Count++;
		}
	}
}

void Evolutive::CEvolLearner::EvalPopulation(void)
{
	register int i,j;
	int *NewScores=NULL;
	double *BitsProb=NULL;	
	int *FieldsDesc=NULL;
	int NumFields,NumBits;
	unsigned char *pData=NULL;
	double IndDiv;
	CScoresVector DivVector;

	//! Evalue the population
	m_Evaluator->EvalPopulation(&m_Population);

	//! Sort the population depending on the problem
	switch(m_OptCriteria)
	{
	case Evolutive::OPT_MINIMIZE: 
		m_Population.SortPopulationScores(Evolutive::SORT_ASC);
		break;
	case Evolutive::OPT_MAXIMIZE:
		m_Population.SortPopulationScores(Evolutive::SORT_DESC);
		break;
	}

	//! If diversity criteria is selected, change the scores and sort the population using new criteria
	switch(m_SelMethod)
	{
	case SELMETHOD_SCORE: //! Selection using only score value
		break;
	case SELMETHOD_DIVERSITY: //! Use a distance between each individual and the previous		
		break;
	case SELMETHOD_FASTDIVERSITY: //! Use a probability over each bit
		// This selection method only can be used with binary variables
		if(m_CodingMethod!=CODE_BINARY && m_CodingMethod!=CODE_GRAY)
			throw CEvolutiveLibException("SELMETHOD_FASTDIVERSITY can only be used with binary variables.",__FILE__,__LINE__,"EvalPopulation");

		// Obtain the problem description from the Evaluator
		FieldsDesc=m_Evaluator->GetProblemDesc(NumFields);

		// Calculate the number of bits
		NumBits=0;
		for(i=0;i<NumFields;i++)
			NumBits+=FieldsDesc[i];

		// Allocate the new data
		BitsProb=new double[NumBits];	
		NewScores=new int[m_PopulationSize];	

		// Initialize the bits value
		memset(BitsProb,0,NumBits*sizeof(double));

		//! Create a bits distribution
		for(i=0;i<m_PopulationSize;i++)	
		{
			// Point to the individual bit string
			pData=m_Population.GetChromosomePtr(i)->GetBitStringPtr();
							
			// Create the bits distribution
			for(j=0;j<NumBits;j++)
			{
				BitsProb[j]+=pData[j];
			}
		}

		//! Normalize the distribution
		for(j=0;j<NumBits;j++)
		{
			BitsProb[j]/=(double)m_PopulationSize;
		}

		//! Calculate the individuals diversity from the distribution
		for(i=0;i<m_PopulationSize;i++)	
		{
			CScoreStruct S;

			//! Point to the individual bit string
			pData=m_Population.GetChromosomePtr(i)->GetBitStringPtr();

			//! Calculate the diversity for this individual
			IndDiv=0.0;
			for(j=0;j<NumBits;j++)
			{
				IndDiv+=fabs(BitsProb[j]-(double)pData[j]);
			}

			//! Store the diversity value
			S.Idx=i;
			S.Value=IndDiv;
			DivVector.push_back(S);			
		}

		//! Remove the temporal data
		if(BitsProb)
			delete[] BitsProb;		
		break;
	}

	//! Sort the population again to apply the diversity criteria
	if(NewScores)
	{
		switch(m_OptCriteria)
		{
		case Evolutive::OPT_MINIMIZE: 			
			m_Population.SortPopulationScores(Evolutive::SORT_ASC);
			break;
		case Evolutive::OPT_MAXIMIZE:
			m_Population.SortPopulationScores(Evolutive::SORT_DESC);
			break;
		}
	}

	//! Release the memory
	if(NewScores)
		delete[] NewScores;
}

void Evolutive::CEvolLearner::SetPopulationSize(int Size)
{
	m_PopulationSize=Size;
}

void Evolutive::CEvolLearner::SetCodingMethod(CODING_METHOD CodingMethod)
{
	m_CodingMethod=CodingMethod;
}
		
void Evolutive::CEvolLearner::SetEvaluator(Evolutive::CEvaluator *Evaluator)
{
	m_Evaluator=Evaluator;
}

void Evolutive::CEvolLearner::SetSelCriteria(SEL_METHOD Criteria)
{
	m_SelMethod=Criteria;
}

bool Evolutive::CEvolLearner::ApplyStopCriteria(void)
{	
	// Apply the stop value criteria
	if(m_StopValueEnabled)
	{
		if(m_StopValue==m_BestScore)
			return true;
	}

	// Apply the max number of iterations criteria
	if(m_IterNum>=m_MaxNumIterations)
		return true;
	
	// If no improvement is done from too much iterations, stop.
	if(m_StableCount>m_StableIters)
		return true;

	// If either of the stopping criterias are acomplished, return false
	return false;
}

void Evolutive::CEvolLearner::SetInitialPopulation(Evolutive::CPopulation *InitialPop)
{
	m_InitialPopulation=InitialPop;
}

void Evolutive::CEvolLearner::SetMaxStaticIterations(int NumIterations)
{
	m_StableIters=NumIterations;
}
